﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace LambdaManAi
{
    public delegate object UserFunction(object[] args);

    public class UserFunctions : LowLevelFunctions
    {
        //Args for the step
        protected const int AiState = 0;
        protected const int WorldState = 1;

        //4-tuple world
        protected const int MapStatus = 0;
        protected const int ManStatus = 1;
        protected const int GhostStatus = 2;
        protected const int FruitSatus = 3;
        
        //Map cells
        protected const int Wall = 0;
        protected const int EmptySpace = 1;
        protected const int Pill = 2;
        protected const int PowerPill = 3;
        protected const int Fruit = 4;
        protected const int ManStartPosition = 5;
        protected const int GhostStartPosition = 6;
        
        //Directions
        protected const int MoveUp = 0;
        protected const int MoveRight = 1;
        protected const int MoveDown = 2;
        protected const int MoveLeft = 3;

        //Man 5-tuple, ghost 3-tuple
        protected const int Vitality = 0;
        protected const int Location = 1;
        protected const int Direction = 2;
        protected const int Lives = 3;
        protected const int Score = 4;

        //Ghost Vitality
        protected const int VitalityStandard = 0;
        protected const int VitalityFright = 1;
        protected const int VitalityInvisible = 2;

        //Cells price
        protected const int PillPriсe = 10;        
        protected const int PowerPillPriсe = 500;
        protected const int FruitPriсe = 1000;


        #region Auxiliary code, don't modify it!

        public static IProcessor Processor { get; set; }

        protected static object Code(Expression<Func<object>> expression, [CallerMemberName]string functionName = "")
        {
            return Processor.ProcessCode(functionName, expression);
        }

        protected static object Code(Locals locals, Expression<Func<object>> expression, [CallerMemberName]string functionName = "")
        {
            return Processor.ProcessCode(functionName, locals, expression);
        }

        #endregion


        public static object AddElemIfListNotEmpty(params object[] args)
        {
            const int elem = 0;
            const int list = 1;
            Locals locals = new Locals(args);
            return Code(() => IsEmptyList(locals[list]) ? locals[list] : Pair(locals[elem], locals[list]));
        }

        public static object GetCellNormInDirection(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            const int direction = 3;
            Locals locals = new Locals(args);

            return Code(() => GetCellNorm(locals[map], NextX(locals[x], locals[direction]), NextY(locals[y], locals[direction])));
        }

        public static object NextX(params object[] args)
        { 
            const int x = 0;
            const int direction = 1;
            Locals locals = new Locals(args);
            return Code(() =>
                (int)locals[direction] == MoveUp || (int)locals[direction] == MoveDown
                    ? locals[x]
                    : (int)locals[direction] == MoveLeft ? (int)locals[x] - 1 : (int)locals[x] + 1
                );
        }
 
        public static object NextY(params object[] args)
        { 
            const int y = 0;
            const int direction = 1;
            Locals locals = new Locals(args);
            return Code(() =>
                (int)locals[direction] == MoveLeft || (int)locals[direction] == MoveRight
                    ? locals[y]
                    : (int)locals[direction] == MoveUp ? (int)locals[y] - 1 : (int)locals[y] + 1
                );
        }
       
        ////Обработка одной вершины при поиске в глубину, возращает ценность вершины
        //public static object CellCost(params object[] args)
        //{
        //    const int map = 0;
        //    const int y = 1;
        //    const int x = 2;
        //    const int prevDirection = 3;
        //    const int distance = 4;
        //    //locals
        //    const int curcell = 5;

        //    Locals locals = null; 
        //    locals = new Locals(args)
        //    {
        //        {curcell, () =>GetCell(locals[map], locals[y], locals[x])}
        //    };

        //    return Code(locals, () => 
        //            (int)Select((int)locals[distance] > 1 || (int)locals[curcell] == Wall, 0, 
        //                (int)Select((int) GetCell(locals[map], (int) locals[y] - 1, locals[x]) == Wall || (int) locals[prevDirection] == MoveDown,
        //                    0,
        //                    CellCost(locals[map], (int) locals[y] - 1, locals[x], MoveUp, (int) locals[distance] + 1)) +
        //                (int)Select((int)GetCell(locals[map], locals[y], (int)locals[x] + 1) == Wall || (int) locals[prevDirection] == MoveLeft,
        //                    0,
        //                    CellCost(locals[map], locals[y], (int)locals[x] + 1, MoveRight, (int)locals[distance] + 1)) + 
        //                (int)Select((int) GetCell(locals[map], (int)locals[y] + 1, locals[x]) == Wall || (int) locals[prevDirection] == MoveUp,
        //                    0,
        //                    CellCost(locals[map], (int)locals[y] + 1, locals[x], MoveDown, (int) locals[distance] + 1)) +
        //                (int)Select((int)GetCell(locals[map], locals[y], (int)locals[x] - 1) == Wall || (int) locals[prevDirection] == MoveRight,
        //                    0,
        //                    CellCost(locals[map], locals[y], (int)locals[x] - 1, MoveLeft, (int)locals[distance] + 1))) +
        //             (int)Select((int)locals[curcell] == Fruit, FruitPriсe * (20000 / (int)locals[distance] / (int)locals[distance]),
        //             (int)Select((int)locals[curcell] == PowerPill, PowerPillPriсe * (20000 / (int)locals[distance] / (int)locals[distance]),
        //             (int)Select((int)locals[curcell] == Pill, PillPriсe * (20000 / (int)locals[distance] / (int)locals[distance]), 0))));
        //}

        public static object GetMaxNeighbor(params object[] args)
        {
            const int map = 0;
            const int manY = 1;
            const int manX = 2;
            var locals = new Locals(args);

            return Code(locals, () =>
                Max4(
                    NormalizeCell(GetCell(locals[map], (int)locals[manY] - 1, locals[manX])),
                    NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] + 1)),
                    NormalizeCell(GetCell(locals[map], (int)locals[manY] + 1, locals[manX])),
                    NormalizeCell(GetCell(locals[map], locals[manY], (int)locals[manX] - 1)))
            );
        }

        public static object NormalizeCell(params object[] args)
        {
            const int cell = 0;
            var locals = new Locals(args);
            return Code(() =>
                (int)locals[cell] >= ManStartPosition ? EmptySpace : locals[cell]
                );
        }

        public static object Max3(params object[] args)
        {
            var locals = new Locals(args);
            return Code(() =>
                Max2(locals[0], Max2(locals[1], locals[2]))
            );
        }

        public static object Max4(params object[] args)
        {
            var locals = new Locals(args);
            return Code(() =>
                Max2(locals[0], Max2(locals[1], Max2(locals[2], locals[3])))
            );
        }

        public static object Max4Index(params object[] args)
        {
            const int max = 4;
            Locals locals = null;
            locals = new Locals(args)
                {
                   {max, () => Max4(locals[0], locals[1], locals[2], locals[3])} 
                };
            return Code(locals, () =>
                Select((int)locals[max] == (int)locals[0], 0,
                Select((int)locals[max] == (int)locals[1], 1,
                Select((int)locals[max] == (int)locals[2], 2, 3)))
            );
        }

        public static object GetCell(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            var locals = new Locals(args);
            return Code(() =>
                GetItem(GetItem(locals[map], (int)locals[y]), (int)locals[x])
            );
        }

        public static object GetCellNorm(params object[] args)
        {
            const int map = 0;
            const int y = 1;
            const int x = 2;
            var locals = new Locals(args);
            return Code(() =>
                NormalizeCell(GetCell(locals[map], (int)locals[y], (int)locals[x]))
            );
        }
    }
}